package com.ch999.comparedata.config;

import com.ch999.comparedata.hander.DeleteHandler;
import com.ch999.comparedata.hander.InsertHander;
import com.ch999.comparedata.hander.SQLHandler;
import com.ch999.comparedata.hander.UpdateHander;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
@Slf4j
@Intercepts({@Signature(
        type= Executor.class,
        method = "update",
        args = {MappedStatement.class,Object.class})})
public class CompareDataInterceptor implements Interceptor {

    @Autowired
    private DataSource dataSource;

    private static final Pattern pattern1 = Pattern.compile("\\?(?=\\s*[^']*\\s*,?\\s*(\\w|$))");

    private static final Pattern pattern2 = Pattern.compile("[\\s]+");
    public static CCJSqlParserManager parserManager = new CCJSqlParserManager();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (StringUtils.isEmpty(ContextHolder.getXid())){
            return invocation.proceed();
        }
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];
        String sql = getParameterizedSql(mappedStatement.getConfiguration(),mappedStatement.getBoundSql(parameter));
        Statement statement = parserManager.parse(new StringReader(sql));
        SQLHandler sqlHandler = null;
        if (statement instanceof Update){
            if (parameter==null){
                return invocation.proceed();
            }
            sqlHandler = new UpdateHander(statement,dataSource,getDataSourceType());
        }
        if (statement instanceof Delete){
            sqlHandler = new DeleteHandler(statement,dataSource,getDataSourceType());
        }
        if (statement instanceof Insert){
            sqlHandler = new InsertHander(statement,dataSource,parameter,getDataSourceType());
        }
        sqlHandler.preHandle();
        Object object = invocation.proceed();
        sqlHandler.postHandle();

        return object;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }


    public String getParameterizedSql(Configuration configuration, BoundSql boundSql)
    {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql
                .getParameterMappings();
        String sql = pattern2.matcher(boundSql.getSql()).replaceAll(" ");

        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass()))
        {
            sql = pattern1.matcher(sql).replaceFirst(Matcher.quoteReplacement(getParameterValue(parameterObject)));
        } else
        {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            for (ParameterMapping parameterMapping : parameterMappings)
            {
                String propertyName = parameterMapping.getProperty();
                if (metaObject.hasGetter(propertyName))
                {
                    Object obj = metaObject.getValue(propertyName);
                    sql = pattern1.matcher(sql).replaceFirst(Matcher.quoteReplacement(getParameterValue(obj)));
                } else if (boundSql.hasAdditionalParameter(propertyName))
                {
                    Object obj = boundSql.getAdditionalParameter(propertyName);
                    sql = pattern1.matcher(sql).replaceFirst(Matcher.quoteReplacement(getParameterValue(obj)));
                } else
                {
                    sql = pattern1.matcher(sql).replaceFirst("缺失");
                }
            }
        }

        return sql;
    }

    public static String getParameterValue(Object obj)
    {
        String value;
        if (obj instanceof String)
        {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date)
        {
            DateFormat formatter = DateFormat.getDateTimeInstance(
                    DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(new Date()) + "'";
        } else
        {
            if (obj != null)
            {
                value = obj.toString();
            } else
            {
                value = "null";
            }

        }
        return value;
    }


    public String getDataSourceType(){
        Connection connection =null;
        try {
            connection = dataSource.getConnection();
            return connection.getMetaData().getDatabaseProductName();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return null;
    }
}
